#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/engine.h>
#include <openssl/err.h>

#include <openssl/applink.c>


void baiduDemo(){
    printf("openssl_test begin\n");
    RSA* rsa=NULL;
    char originstr[]="hello\n";   //这是我们需要加密的原始数据
    //allocate RSA structure，首先需要申请一个RSA结构题用于存放生成的公私钥，这里rsa就是这个结构体的指针
    rsa = RSA_new();
    if(rsa==NULL)
    {
        printf("RSA_new failed\n");
        return;
    }

    //generate RSA keys
    BIGNUM* exponent;
    exponent = BN_new();        //生成RSA公私钥之前需要选择一个奇数（odd number）来用于生成公私钥,相当于openssl genrsa 命令的 -F4 (E value)
    if(exponent ==NULL)
    {
        printf("BN_new failed\n");
        goto FAIL1;
    }
    if(0==BN_set_word(exponent,RSA_F4))    //这里选择奇数65537
    {
        printf("BN_set_word failed\n");
        goto FAIL1;
    }


    //这里modulus的长度选择4096，小于1024的modulus长度都是不安全的，容易被破解
    if(0==RSA_generate_key_ex(rsa,4096,exponent,NULL))
    {
        printf("RSA_generate_key_ex failed\n");
        goto FAIL;
    }
    char* cipherstr = NULL;
    //分配一段空间用于存储加密后的数据，这个空间的大小由RSA_size函数根据rsa算出
    cipherstr = malloc((size_t)RSA_size(rsa) + 1);
    int cipherstr_enclen = -1;   // 实际加密后的字节数
    if(cipherstr==NULL)
    {
        printf("malloc cipherstr buf failed\n");
        goto FAIL1;
    }
    //下面是实际的加密过程，最后一个参数padding type，有以下几种。
    /*
    RSA_PKCS1_PADDING PKCS #1 v1.5 padding. This currently is the most widely used mode.
    RSA_PKCS1_OAEP_PADDING
    EME-OAEP as defined in PKCS #1 v2.0 with SHA-1, MGF1 and an empty encoding parameter. This mode is recommended for all new applications.
    RSA_SSLV23_PADDING
    PKCS #1 v1.5 padding with an SSL-specific modification that denotes that the server is SSL3 capable.
    RSA_NO_PADDING
    Raw RSA encryption. This mode should only be used to implement cryptographically sound padding modes in the application code. Encrypting user data directly with RSA is insecure.
    */
    //这里首先用公钥进行加密，选择了RSA_PKCS1_PADDING


    //from 的 flen字节数必须小于等于 RSA_size(rsa) - 11
    cipherstr_enclen = RSA_public_encrypt((int)strlen(originstr)+1,
                                          (const unsigned char *)originstr,
                                          (unsigned char *)cipherstr,rsa,RSA_PKCS1_PADDING);
    if( 0 > cipherstr_enclen )
    {
        printf("encryption failure\n");
        goto FAIL2;
    }
    cipherstr[cipherstr_enclen] = 0;   //保证打印字符不会出错
    printf("the original string is %s\n",originstr);
    printf("the encrypted string is %s\n",cipherstr);


    //Now, let's decrypt the string with private key
    //下面来用私钥解密，首先需要一个buffer用于存储解密后的数据，这个buffer的长度要足够（小于等于RSA_size(rsa))
    //这里分配一个长度为RSA_size(rsa)的字符缓冲，保证解密缓冲够用。
    char *decrypted_str = NULL;
    decrypted_str = malloc((size_t)RSA_size(rsa));
    int decrypted_len = -1;
    decrypted_len=RSA_private_decrypt(cipherstr_enclen,
                                      (const unsigned char *)cipherstr,
                                      (unsigned char *)decrypted_str,rsa,RSA_PKCS1_PADDING);
    if(0 > decrypted_len)
    {
        printf("decryption failure\n");
        goto FAIL2;
    }
    printf("decrypted string length is %d,decryped_str is '%s'\n",decrypted_len,decrypted_str);

    free(decrypted_str);
FAIL2:
    free(cipherstr);
FAIL1:
    BN_free(exponent);
FAIL:
    RSA_free(rsa);
    //       return 0;
}

void demo1(){
//    //私钥  长度为512  （使用自己生成的公秘钥）
//    char private_key[] = "-----BEGIN PRIVATE KEY-----\n"\
//                         "MIIBVgIBADANBgkqhkiG9w0BAQEFAASCAUAwggE8AgEAAkEAzpXEGSjFGDRdr4Gp\n"\
//                         "k/impFrqxw0JUs7oUwTheJgqNIfCJfw00PHOKmIDI9UoS+E3Ozs1reMP1r0IuUys\n"\
//                         "zX5LGwIDAQABAkEAhUCjcdsTbu5wM8H+QT0VOFSQtf5ZsjlWMB6o1SHJr4Fs8UEE\n"\
//                         "7JHUTGrlUopMrhQDfYxGqDUAXykca99xu8e1QQIhAPPdcze2DeV5X9a8bcd3VEzK\n"\
//                         "Luy6SMOtomEMUKPlYEa7AiEA2N1puJwKEulYWaHywLwhA8tM08YFsFqh18JsUbP5\n"\
//                         "NyECIQCtuykXGnLB9XsqfyjyPnfnEO7nJTsrdGrHGr/kU0gIewIgVWqYGntzSFGa\n"\
//                         "V+t+psUyp8DqaLslQHniJw5QBbpCXaECIQDgDWRfFb7h68XMi2fpkd727YDpl64p\n"\
//                         "fb2H/qFyq3xBDw==\n"\
//                         "-----END PRIVATE KEY-----";

//    //将字符串键加载到bio对象
//    BIO* pKeyBio = BIO_new_mem_buf(private_key, (int)strlen(private_key));  //只读的内存型BIO
//    if (pKeyBio == NULL){
//        return ;
//    }



    RSA* rsa=NULL;
    //allocate RSA structure，首先需要申请一个RSA结构题用于存放生成的公私钥，这里rsa就是这个结构体的指针
    rsa = RSA_new();

    //1.创建一个内存型BIO并写入数据
    BIO *mbio = BIO_new(BIO_s_mem());
//    BIO_puts(mbio, "Hello World\n");

    do{
        if(rsa==NULL)
        {
            printf("RSA_new failed\n");
            break;
        }

        //generate RSA keys
        BIGNUM* exponent;
        exponent = BN_new();        //生成RSA公私钥之前需要选择一个奇数（odd number）来用于生成公私钥,相当于openssl genrsa 命令的 -F4 (E value)
        if(exponent ==NULL)
        {
            printf("BN_new failed\n");
            break;
        }
        if(0==BN_set_word(exponent,RSA_F4))    //这里选择奇数65537
        {
            printf("BN_set_word failed\n");
            break;
        }
        if(0==RSA_generate_key_ex(rsa,1024,exponent,NULL))
        {
            printf("RSA_generate_key_ex failed\n");
            break;
        }


        BUF_MEM *bptr;

        //x 参数x是NULL，那么该参数将被忽略。如果x不是NULL，但是*x是NULL，那么返回的结构体就会写入到*x里面。
        //  如果x和*x都不是NULL，那么函数就试图重用*x中的结构体。这种函数总是返回一个执行结构体的指针（x的值），如果出错，就返回NULL。
        //  PEM读操作的系列函数都有TYPE **x和返回TYEP *指针的参数。
        //enc 参数定义了PEM函数写私钥的时候采用的加密算法。加密是在PEM层进行的。如果该参数为NULL，那么私钥就会以不加密的形式写入相应的接口。
        //cb cb参数定义了回调函数，该回调函数在加密PEM结构体（一般来说是私钥）需要口令的时候使用
        //u cb参数为NULL，而u参数不为NULL，那么u参数就是一个以NULL结束的字符串用作口令。
        //  如果cb和u参数都是NULL，那么缺省的回调函数就会并使用，该函数一般在当前的终端提示输入口令，并且关掉了回显功能。
        //  回调函数的形式如下：
        //  int cb(char *buf, int size, int rwflag, void *u);
        //   在该函数中，buf是保存口令的参数。size是考虑最大的长度（如buf的长度）。
        //    rwflag是一个读写标志，0的时候为读操作，1的时候为写操作。当rwflag为1的时候，
        //    典型的函数一般会要求用户验证口令（如输入两次）。u参数跟上述PEM函数的u参数意义是一样的，
        //    它允许应用程序使用固定的数据作为参数传给回调函数。回调函数必须返回口令字符的数目，如果出错返回0。

        //The read routines return either a pointer to the structure read or NULL if an error occurred.
        //The write routines return 1 for success or 0 for failure.
        if(0 == PEM_write_bio_RSAPrivateKey(mbio,rsa,
                                            EVP_aes_192_cbc(),
                                            (unsigned char *)"1234",4,
                                            NULL,NULL)
                ){
            printf("PEM_write_bio_RSAPrivateKey failed\n");
            break;
        }


        BIO_get_mem_ptr(mbio, &bptr);
        printf("---EVP_aes_192 private key---\n%s\n",bptr->data);

        RSA* rsa1 = PEM_read_bio_RSAPrivateKey(mbio,NULL,NULL,"1234");
        if(rsa1){
            BIO_reset(mbio);
            if(1 == PEM_write_bio_RSAPrivateKey(mbio,rsa,
                                                NULL,
                                                NULL,0,
                                                NULL,NULL)
                    ){
                BIO_get_mem_ptr(mbio, &bptr);
                printf("---no passphrase private key---\n%s\n",bptr->data);
            }
//            RSA_free(rsa1);
        }

        BIO_reset(mbio);
        if(1 == PEM_write_bio_RSA_PUBKEY(mbio,rsa1)){  //生成公钥 PEM_write_bio_RSAPublicKey(mbio,rsa1)
            printf("---public key---\n%s\n",bptr->data);
        }
        if(rsa1) RSA_free(rsa1);

    }while(0);

    BIO_free(mbio);
    RSA_free(rsa);

    //3.把一个BUF_MEM结构从一个BIO中取出并释放该BIO
//    BIO *mem;
//    char data[] = "Hello World";
//    mem = BIO_new_mem_buf(data, -1);
//    BUF_MEM *bptr;
//    BIO_get_mem_ptr(mem, &bptr);
//    BIO_set_close(mem, BIO_NOCLOSE); /* BIO_free() 不释放BUF_MEM结构 */
//    BIO_free(mem);

}

void demo2(){
    /* 生成公钥 */
    RSA* rsa = RSA_generate_key( 1024, RSA_F4, NULL, NULL);
    BIO *bp = BIO_new( BIO_s_file() );
    BIO_write_filename( bp, "public.pem" );
//    PEM_write_bio_RSAPublicKey(bp, rsa);
    PEM_write_bio_RSA_PUBKEY(bp,rsa);
    BIO_free_all(bp);

    /* 生成私钥 */
    char passwd[]="1234";
    bp = BIO_new_file("private.pem", "w+");
    PEM_write_bio_RSAPrivateKey(bp, rsa, EVP_aes_192_cbc(), (unsigned char*)passwd, 4, NULL, NULL);
    BIO_free_all(bp);

    RSA_free(rsa);

    /*  从文件读取私钥  */
    OpenSSL_add_all_algorithms();
    bp = BIO_new(BIO_s_file());
    BIO_read_filename( bp, "private.pem" );
    RSA* rsaK = PEM_read_bio_RSAPrivateKey( bp, NULL, NULL, passwd );

    // 用私钥加密 123456
    void *buf = malloc((size_t)RSA_size(rsaK) * 8);
    void *buf1 = malloc((size_t)RSA_size(rsaK) * 8);
    int n = RSA_private_encrypt(6,(const unsigned char *)"123456",buf,rsaK,RSA_PKCS1_PADDING); // 等同于openssl rsautl -sign
    BIO_free_all(bp);
    RSA_free(rsaK);
    int n1=EVP_EncodeBlock(buf1,buf,n);
    ((char *)buf1)[n1] = 0;
    printf("privatekey encrypt resualt(BASE64) : %s\n",(char *)buf1);

    //写原始文件
    bp = BIO_new(BIO_s_file());
    BIO_write_filename( bp, "data.txt" );
    BIO_puts(bp,"123456");
    BIO_free_all(bp);
    //写加密文件
    bp = BIO_new(BIO_s_file());
    BIO_write_filename( bp, "data.encrypt" );
    BIO_write(bp,buf,n);
    BIO_free_all(bp);


    /*  从文件读取公钥  */
    OpenSSL_add_all_algorithms();
    bp = BIO_new( BIO_s_file() );
    BIO_read_filename( bp, "public.pem" );
    rsaK = PEM_read_bio_RSA_PUBKEY( bp, NULL, NULL, NULL );
    n = RSA_public_decrypt(n,buf,buf1,rsaK,RSA_PKCS1_PADDING);  // 等同于openssl rsautl -verify -pubin
    ((char *)buf1)[n] = 0;
    printf("public decrypt resualt : %s\n",(char *)buf1);
    BIO_free_all(bp);
    RSA_free(rsaK);

    printf("\n");
    fflush(stdout);
    free(buf);
    free(buf1);
}

void digestDemo1(){
    EVP_MD_CTX *mdctx;
    const EVP_MD *md = EVP_sha256();//EVP_sha256(); // EVP_get_digestbyname("sha256")
    char mess1[] = "Test Message\n";
    char mess2[] = "Hello World\n";
    char mess3[] = "Test Message\nHello World\n";
    unsigned char md_value[EVP_MAX_MD_SIZE];
    unsigned int md_len, i;

    mdctx = EVP_MD_CTX_new();
    EVP_DigestInit_ex(mdctx, md, NULL);
    EVP_DigestUpdate(mdctx, mess1, strlen(mess1));
    EVP_DigestUpdate(mdctx, mess2, strlen(mess2));
    EVP_DigestFinal_ex(mdctx, md_value, &md_len);
    EVP_MD_CTX_free(mdctx);

    printf("Digest is: ");
    for (i = 0; i < md_len; i++)
        printf("%02x", md_value[i]);
    printf("\n");


    EVP_Digest(mess3,strlen(mess3),md_value,&md_len,md,NULL);   // 一次过计算
    printf("Digest is: ");
    for (i = 0; i < md_len; i++)
        printf("%02x", md_value[i]);
    printf("\n");

    /*  从文件读取私钥  */
    OpenSSL_add_all_algorithms();
    BIO * bp = BIO_new(BIO_s_file());
    BIO_read_filename( bp, "private.pem" );
    RSA* rsa = PEM_read_bio_RSAPrivateKey( bp, NULL, NULL, "1234" );

    /*  从文件读取公钥  */
    OpenSSL_add_all_algorithms();
    FILE *fp= NULL;
    fopen_s(&fp,"public.pem","r");
    fseek(fp,0L,SEEK_END);
    int len = ftell(fp);

//    BIO *bp = BIO_new_fp(stdin, BIO_NOCLOSE);
    bp = BIO_new( BIO_s_file() );
    BIO_set_fp(bp,fp,BIO_CLOSE);
    BIO_reset(bp);
    RSA* rsaPub = PEM_read_bio_RSA_PUBKEY(bp,NULL,NULL,NULL);
    BIO_reset(bp);

    if(len > 0){
        printf("\n\n simulate ,openssl dgst -sign\n");
        void *buf = malloc((size_t)len);
        void *buf1 = malloc((size_t)len * 8);
        void *buf2 = malloc((size_t)len * 8);
        BIO_read(bp,buf,len);

        EVP_PKEY *pkey=EVP_PKEY_new();
        EVP_PKEY_assign_RSA(pkey,rsa);
        EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
        EVP_SignInit_ex(mdctx,EVP_sha256(), NULL);
        EVP_SignUpdate(mdctx,buf,(size_t)len);
        unsigned int outlen;
        EVP_SignFinal(mdctx,buf1,&outlen,pkey);
        int n1=EVP_EncodeBlock(buf2,buf1,(int)outlen);
        ((char *)buf2)[n1] = 0;
        printf("digest sign (BASE64) : %s\n",(char *)buf2);

        EVP_MD_CTX_reset(mdctx);
        EVP_PKEY_free(pkey);
        pkey=EVP_PKEY_new();
//        ((char *)buf1)[1] = 1;
        EVP_PKEY_assign_RSA(pkey,rsaPub);
        EVP_VerifyInit_ex(mdctx,EVP_sha256(), NULL);
        EVP_VerifyUpdate(mdctx,buf,(size_t)len);
        len = EVP_VerifyFinal(mdctx,buf1,outlen,pkey);
        if(len==1)
            printf("verify ok!\n");
        else
            printf("verify failed!\n");

        free(buf);
        free(buf1);
        free(buf2);
    }

    BIO_free_all(bp);
    RSA_free(rsa);
    RSA_free(rsaPub);

}

int main()
{
    printf("Hello OpenSSL!\n");

    baiduDemo();
    demo1();
    demo2();
    digestDemo1();

    system("Pause");

    return 0;
}
